package pwd.allen.simple;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import pwd.allen.simple.handler.ChatServerHandler;
import pwd.allen.simple.handler.SimpleServerHandler;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

/**
 * netty服务类
 *
 * Bootstrap：主要作用是配置整个 Netty 程序，串联各个组件
 *
 * NioEventLoop：表示一个不断循环执行处理任务的线程，
 *  每个NioEventLoop都有一个selector，用于监听绑定在其上的socket网络通道。
 *  NioEventLoop 内部采用串行化设计，从消息的读取->解码->处理->编码->发送，
 *  始终由 IO 线程 NioEventLoop负责。
 *  每个 NioEventLoop 中包含有一个 Selector，一个 taskQueue
 *  每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
 *  每个 NioChannel 只会绑定在唯一的 NioEventLoop 上，每个 NioChannel 都绑定有一个自己的 ChannelPipeline
 *
 * NioEventLoopGroup：包含多个NioEventLoop的线程组，默认线程数为 cpu核数*2
 *
 * Channel：Netty 网络通信的组件，能够用于执行网络 I/O 操作。可获得当前网络连接的通道的状态，可获得 网络连接的配置参数（例如接收缓冲区大小）
 *
 * @author 门那粒沙
 * @create 2019-10-27 18:18
 **/
public class NettyServer {

    public static Charset charset = Charset.forName("GBK");

    //创建业务线程池，添加handler时可以指定该线程池；否则默认是使用 IO 线程；
    // 这种方式会将整个 handler 都交给业务线程池，不够灵活
    private static EventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(10);

    //监听的端口
    private int port;

    public NettyServer(int port) {
        this.port = port;
    }

    public void run() {
        //创建两个线程组，两个都是无限循环 含有的子线程(NioEventLoop)的个数默认为cpu核数 * 2
        // 1. BossGroup：处理连接请求
        // 2. WorkerGroup：网络读写操作、业务处理
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //创建服务器端的启动对象，配置参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            //使用链式编程来进行设置
            serverBootstrap.group(bossGroup, workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class)//设置服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128)//设置服务器可连接队列的大小，来不及处理的连接会放在队列中
                    .childOption(ChannelOption.SO_KEEPALIVE, true);//设置保持活动连接状态

            //handler对应 bossGroup , childHandler 对应 workerGroup
            serverBootstrap.handler(new LoggingHandler(LogLevel.INFO)) //bossGroup的日志处理
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象
                        /**
                         * 客户端建立通道时会调用
                         * 可以给pipeline设置处理器
                         * @param socketChannel
                         * @throws Exception
                         */
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //向pipeline加入解码器
//                            pipeline.addLast("decoder", new StringDecoder());
//                            //向pipeline加入编码器
//                            pipeline.addLast("encoder", new StringEncoder());
//                            pipeline.addLast("chat", new ChatServerHandler());

                            /*
                            心跳检测处理
                            IdleStateHandler，一定时间内没发生读/写则会发送一个心跳检测包检测是否连接
                            没有则触发IdleStateEvent，下个handle可以通过userEventTiggered方法处理该事件
                            */
                            pipeline.addLast("idle", new IdleStateHandler(3, 0, 0, TimeUnit.SECONDS));
                            pipeline.addLast("simple", new SimpleServerHandler());
                        }
                    });//给WorkerGroup的EventLoop对应的管道设置处理器


            //绑定端口并启动服务器，如果调用了sync()则同步执行，否则异步执行（会立即返回）
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(port));

            //监听事件
            channelFuture.addListener(new ChannelFutureListener() {
                /**
                 * 启动完成
                 * @param future
                 * @throws Exception
                 */
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (channelFuture.isSuccess()) {
                        System.out.println("服务端开启成功!!!");
                    } else {
                        System.out.println("服务端开启失败!!!");
                    }
                }
            });

            System.out.println("before call sync()：" + channelFuture.isSuccess());
            channelFuture.sync();
            System.out.println("after call sync()：" + channelFuture.isSuccess());

            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //断开连接，关闭线程
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new NettyServer(10100).run();
    }
}
